<script setup>
import {
    computed,
    getCurrentInstance,
    inject,
    provide,
    reactive,
    ref,
    useAttrs,
    useSlots,
    watch
} from 'vue'
import { useDraggable } from 'vue-draggable-plus'
import {randomCode} from '@/utils/common/string.js'

// 格式化
import { VXETable } from 'vxe-table'
import XEUtils from 'xe-utils'

// 格式化日期，默认 yyyy-MM-dd
VXETable.formats.add('date', {
    cellFormatMethod ({ cellValue }, format) {
        return XEUtils.toDateString(cellValue, format || 'yyyy-MM-dd')
    }
})
// 四舍五入金额，每隔3位逗号分隔，默认2位数
VXETable.formats.add('amount', {
    cellFormatMethod ({ cellValue }, digits = 2) {
        return XEUtils.commafy(XEUtils.toNumber(cellValue), { digits })
    }
})
// 格式化银行卡，默认每4位空格隔开
VXETable.formats.add('bankcard', {
    cellFormatMethod ({ cellValue }) {
        return XEUtils.commafy(XEUtils.toValueString(cellValue), { spaceNumber: 4, separator: ' ' })
    }
})
// 向下舍入,默认两位数
VXETable.formats.add('cutNumber', {
    cellFormatMethod ({ cellValue }, digits = 2) {
        return XEUtils.toFixed(XEUtils.floor(cellValue, digits), digits)
    }
})
// 四舍五入,默认两位数
VXETable.formats.add('fixedNumber', {
    cellFormatMethod ({ cellValue }, digits = 2) {
        return XEUtils.toFixed(XEUtils.round(cellValue, digits), digits)
    }
})
// 格式化性别
VXETable.formats.add('sex', {
    cellFormatMethod ({ cellValue }) {
        return cellValue ? (cellValue === '1' ? '男' : '女') : ''
    }
})
//设置全局的tooltip的层级
VXETable.config({
    tooltip: {
        zIndex: 99999,
    },
})
const attrs = useAttrs()
const slots = useSlots()
/**
 * 从VxeTable继承过来的事件，挂载到当前组件上，不能通过Object.keys(VxeTable instance)获取，vue做了限制，返回的keys是空
 */
const extendEvents = [
    'getRefMaps',
    'getComputeMaps',
    'openExport',
    'openPrint',
    'exportData',
    'openImport',
    'importData',
    'saveFile',
    'readFile',
    'importByFile',
    'print',
    'clearValidate',
    'fullValidate',
    'validate',
    'dispatchEvent',
    'clearAll',
    'syncData',
    'updateData',
    'loadData',
    'reloadData',
    'reloadRow',
    'loadTreeChildren',
    'loadColumn',
    'reloadColumn',
    'getRowNode',
    'getColumnNode',
    'getRowSeq',
    'getRowIndex',
    'getVTRowIndex',
    'getVMRowIndex',
    'getColumnIndex',
    'getVTColumnIndex',
    'getVMColumnIndex',
    'createData',
    'createRow',
    'revertData',
    'clearData',
    'isInsertByRow',
    'removeInsertRow',
    'isUpdateByRow',
    'getColumns',
    'getColumnById',
    'getColumnByField',
    'getTableColumn',
    'getData',
    'getCheckboxRecords',
    'getParentRow',
    'getRowById',
    'getRowid',
    'getTableData',
    'setColumnFixed',
    'clearColumnFixed',
    'hideColumn',
    'showColumn',
    'setColumnWidth',
    'getColumnWidth',
    'resetColumn',
    'refreshColumn',
    'refreshScroll',
    'recalculate',
    'openTooltip',
    'closeTooltip',
    'isAllCheckboxChecked',
    'isAllCheckboxIndeterminate',
    'getCheckboxIndeterminateRecords',
    'setCheckboxRow',
    'isCheckedByCheckboxRow',
    'isIndeterminateByCheckboxRow',
    'toggleCheckboxRow',
    'setAllCheckboxRow',
    'getRadioReserveRecord',
    'clearRadioReserve',
    'getCheckboxReserveRecords',
    'clearCheckboxReserve',
    'toggleAllCheckboxRow',
    'clearCheckboxRow',
    'setCurrentRow',
    'isCheckedByRadioRow',
    'setRadioRow',
    'clearCurrentRow',
    'clearRadioRow',
    'getCurrentRecord',
    'getRadioRecord',
    'getCurrentColumn',
    'setCurrentColumn',
    'clearCurrentColumn',
    'sort',
    'clearSort',
    'isSort',
    'getSortColumns',
    'closeFilter',
    'isActiveFilterByColumn',
    'isFilter',
    'isRowExpandLoaded',
    'clearRowExpandLoaded',
    'reloadRowExpand',
    'reloadExpandContent',
    'toggleRowExpand',
    'setAllRowExpand',
    'setRowExpand',
    'isExpandByRow',
    'clearRowExpand',
    'clearRowExpandReserve',
    'getRowExpandRecords',
    'getTreeExpandRecords',
    'isTreeExpandLoaded',
    'clearTreeExpandLoaded',
    'reloadTreeExpand',
    'reloadTreeChilds',
    'toggleTreeExpand',
    'setAllTreeExpand',
    'setTreeExpand',
    'isTreeExpandByRow',
    'clearTreeExpand',
    'clearTreeExpandReserve',
    'getScroll',
    'scrollTo',
    'scrollToRow',
    'scrollToColumn',
    'clearScroll',
    'updateFooter',
    'updateStatus',
    'setMergeCells',
    'removeMergeCells',
    'getMergeCells',
    'clearMergeCells',
    'setMergeFooterItems',
    'removeMergeFooterItems',
    'getMergeFooterItems',
    'clearMergeFooterItems',
    'updateCellAreas',
    'focus',
    'blur',
    'connect',
    'getSetupOptions',
    'updateAfterDataIndex',
    'callSlot',
    'getParentElem',
    'getParentHeight',
    'getExcludeHeight',
    'defineField',
    'handleTableData',
    'cacheRowMap',
    'analyColumnWidth',
    'saveCustomResizable',
    'saveCustomFixed',
    'saveCustomVisible',
    'handleCustom',
    'handleUpdateDataQueue',
    'handleRefreshColumnQueue',
    'preventEvent',
    'checkSelectionStatus',
    'handleSelectRow',
    'triggerHeaderHelpEvent',
    'triggerHeaderTooltipEvent',
    'triggerBodyTooltipEvent',
    'triggerFooterTooltipEvent',
    'handleTargetLeaveEvent',
    'triggerHeaderCellClickEvent',
    'triggerHeaderCellDblclickEvent',
    'triggerCellClickEvent',
    'triggerCellDblclickEvent',
    'handleToggleCheckRowEvent',
    'triggerCheckRowEvent',
    'triggerCheckAllEvent',
    'triggerRadioRowEvent',
    'triggerCurrentRowEvent',
    'triggerRowExpandEvent',
    'triggerTreeExpandEvent',
    'triggerSortEvent',
    'triggerScrollXEvent',
    'triggerScrollYEvent',
    'scrollToTreeRow',
    'updateScrollYStatus',
    'updateScrollXSpace',
    'updateScrollYSpace',
    'updateScrollXData',
    'updateScrollYData',
    'checkScrolling',
    'updateZindex',
    'handleCheckedCheckboxRow',
    'triggerHoverEvent',
    'setHoverRow',
    'clearHoverRow',
    'getCell',
    'getCellLabel',
    'findRowIndexOf',
    'eqRow',
    'openFilter',
    'setFilter',
    'clearFilter',
    'getCheckedFilters',
    'checkFilterOptions',
    'triggerFilterEvent',
    'handleClearFilter',
    'confirmFilterEvent',
    'closeMenu',
    'moveCtxMenu',
    'handleGlobalContextmenuEvent',
    'ctxMenuMouseoverEvent',
    'ctxMenuMouseoutEvent',
    'ctxMenuLinkEvent',
    'insert',
    'insertAt',
    'remove',
    'removeCheckboxRow',
    'removeRadioRow',
    'removeCurrentRow',
    'getRecordset',
    'getInsertRecords',
    'getRemoveRecords',
    'getUpdateRecords',
    'getActiveRecord',
    'getEditRecord',
    'getSelectedCell',
    'clearActived',
    'clearEdit',
    'clearSelected',
    'isActiveByRow',
    'isEditByRow',
    'setActiveRow',
    'setEditRow',
    'setActiveCell',
    'setEditCell',
    'setSelectCell',
    'handleActived',
    'handleFocus',
    'handleSelected',
    'addCellSelectedClass',
    'moveTabSelected',
    'moveCurrentRow',
    'moveSelected',
    'triggerHeaderCellMousedownEvent',
    'triggerCellMousedownEvent',
    'validCellRules',
    'hasCellRules',
    'triggerValidate',
    'showValidTooltip',
    'renderVN'
]

const props = defineProps({
    border: {
        type: Boolean,
        default: true
    },
    height: {
        type: [String, Number],
        default: 'auto'
    },
    size: {
        type: String,
        default: 'small'
    },
    columnConfig: {
        type: Object,
        default: undefined
    },
    showHeaderOverflow: {
        type: [Boolean, String],
        default: 'tooltip'
    },
    showOverflow: {
        type: [Boolean, String],
        default: 'tooltip'
    },
    rowConfig: {
        type: Object,
        default: undefined
    },
    align: {
        type: String,
        default: 'center'
    },
    startIndex: {
        type: Number,
        default: undefined
    },
    seqConfig: {
        type: Object,
        default: undefined
    },
    onHeaderCellClick: {
        type: Function,
        default: undefined
    },
    treeConfig: {
        type: Object,
        default: undefined
    },
    sortConfig: {
        type: Object,
        default: undefined
    },
    data: {
        type: Array,
        default: undefined
    },
    loading: {
        type: Boolean,
        default: undefined
    },
    dragable: {
        type: Boolean,
        default: undefined
    },
    rankField: {
        type: String,
        default: undefined
    },
    onDragEnd: {
        type: Function,
        default: undefined
    },
    onSortChange: {
        type: Function,
        default: undefined
    }
})

const columnConfig = computed(() => {
    return Object.assign(
        {},
        {
            resizable: true
        },
        props.columnConfig
    )
})

const rowConfig = computed(() => {
    return Object.assign(
        {},
        {
            isHover: true,
            isCurrent: true,
            keyField: 'id',
            useKey: true
        },
        props.rowConfig
    )
})
const seqConfig = computed(() => {
    return Object.assign(
        {},
        {
            startIndex: props.startIndex
        },
        props.seqConfig
    )
})

const sortConfig = computed(() => {
    return Object.assign(
        {},
        {
            remote: true
        },
        props.sortConfig
    )
})

const loading = computed(() => {
    return props.loading || dragState.updating
})

const currentPage = inject('currentPage', null)
const table = ref(null)
const tableClassName = `table_${randomCode(10)}`
const tableClass = computed(() => {
    const list = [tableClassName, attrs.class]
    if (props.dragable) {
        list.push('dragable')
    }
    return list
})

function handleHeaderCellClick(args) {
    if (props.onHeaderCellClick) {
        props.onHeaderCellClick(args)
    }
}

/**         排序相关 start */
function handleSortChange(args) {
    if (props.onSortChange) {
        props.onSortChange(args)
    } else {
        defaultSort(args)
    }
}
function defaultSort({ property, order }) {
    if (!property) {
        window.$error('该列未声明field属性，无法支持排序')
        return
    }
    currentPage?.exposed.setSort([
        {
            sortColumn: property,
            sortType: order
        }
    ])
}
/**         排序相关 end */

/**        拖拽相关 start           */
const dragListener = ref(null)
watch(
    () => props.dragable,
    (newV, oldV) => {
        if (newV && !oldV) {
            registerDragListener()
        } else if (!newV && oldV) {
            unRegisterDragListener()
        }
    },
    {
        immediate: true
    }
)

const dragState = reactive({
    dragItem: null,
    insertTo: {
        parentItem: null, //插入到哪个父元素下
        beforeItem: null, //前一个元素是谁
        afterItem: null //后一个元素是谁
    },
    updating: false //是否正在更新
})

/**
 * 注册拖拽监听
 */
function registerDragListener() {
    dragListener.value?.destroy()
    dragListener.value = useDraggable(
        `.${tableClassName} .body--wrapper>.vxe-table--body tbody`,
        ref([]),
        {
            onChoose: (evt) => {
                const targetTrElem = evt.item
                const targetRowNode = table.value?.getRowNode(targetTrElem)
                table.value?.setCurrentRow(targetRowNode?.item)
                //记录当前拖动的哪个节点
                dragState.dragItem = targetRowNode
            },
            onMove: (evt) => {
                let parentNode = evt.to.querySelector(`.moveable--parent`)
                if (parentNode != null) {
                    //清空样式
                    parentNode.classList.remove('moveable--parent')
                }
                dragState.insertTo.parentItem = null
                let before = null,
                    after = null
                if (evt.willInsertAfter) {
                    //后移
                    before = table.value?.getRowNode(evt.related)
                    const afterDom = evt.related.nextElementSibling
                    after = afterDom ? table.value?.getRowNode(afterDom) : null
                } else {
                    //前移
                    after = table.value?.getRowNode(evt.related)
                    const beforeDom = evt.related.previousElementSibling
                    before = beforeDom ? table.value?.getRowNode(beforeDom) : null
                }
                dragState.insertTo.beforeItem = before
                dragState.insertTo.afterItem = after
                if (before == null) {
                    //拖到第一个了，不需要特殊处理了
                } else if (
                    before?.parent?.[rowConfig.value.keyField] ==
                    after?.parent?.[rowConfig.value.keyField]
                ) {
                    //如果前后的父元素一样
                    dragState.insertTo.parentItem = before.parent
                } else {
                    if (
                        after?.parent?.[rowConfig.value.keyField] ==
                        before?.item[rowConfig.value.keyField]
                    ) {
                        //前一个是后一个的父元素，它排第一了
                        dragState.insertTo.parentItem = after?.parent
                        dragState.insertTo.beforeItem = null
                    } else {
                        //先跟着前面的走，它排最后一个了
                        dragState.insertTo.parentItem = before?.parent
                        dragState.insertTo.afterItem = null
                    }
                }
                if (dragState.insertTo.parentItem) {
                    //类似 3.2.10的结构 表示一级目录的第三个，二级目录的第2个，三级目录的第三个

                    //根据 parentRowIndex 找到dom元素
                    parentNode = evt.to.querySelector(
                        `tr[rowid="${
                            dragState.insertTo.parentItem[rowConfig.value.keyField]
                        }"]`
                    )
                    if (parentNode) {
                        parentNode.classList.add('moveable--parent')
                    }
                }
            },
            onEnd: (sortableEvent) => {
                if (!props.rankField) {
                    window.$error('未设置table的rankField属性，无法实现拖拽', 3000)
                    return
                }
                if (!dragState.dragItem.item[props.rankField]) {
                    window.$error(
                        `拖拽的数据没有${props.rankField}属性，无法实现拖拽`,
                        3000
                    )
                    return
                }
                if (!rowConfig.value.keyField) {
                    window.$error(
                        '未设置table的rowConfig.keyField属性，无法实现拖拽',
                        3000
                    )
                    return
                }
                if (!dragState.dragItem.item[rowConfig.value.keyField]) {
                    window.$error(
                        `拖拽的数据没有${rowConfig.value.keyField}属性，无法实现拖拽`,
                        3000
                    )
                    return
                }
                if (props.treeConfig && !props.treeConfig.childrenField) {
                    window.$error(
                        '检测到树形table但未设置treeConfig.childrenField属性，无法实现拖拽',
                        3000
                    )
                    return
                }

                const rankField = props.rankField
                const keyField = rowConfig.value.keyField
                //有值代表是树形结构，它的值就是子节点的字段名
                const treeField = props.treeConfig?.childrenField

                if (
                    checkIntoSon(
                        dragState.dragItem.item,
                        dragState.insertTo.parentItem?.[keyField]
                    )
                ) {
                    window.$error('不允许拖入子节点下', 3000)
                    return
                }

                let parentNode = sortableEvent.to.querySelector(`.moveable--parent`)
                if (parentNode != null) {
                    //清空样式
                    parentNode.classList.remove('moveable--parent')
                }
                const { dragItem, insertTo } = dragState
                const updateData = {
                    id: dragItem.item[keyField],
                    item: dragItem.item,
                    oriPid: (treeField ? dragItem.parent?.[keyField] : null) ?? 0,
                    oriParentItem: treeField ? dragItem.parent : null,
                    newPid: (treeField ? insertTo.parentItem?.[keyField] : null) ?? 0,
                    newParentItem: treeField ? insertTo.parentItem : null,
                    beforeItem: insertTo.beforeItem?.item,
                    oriRank: dragItem.item[rankField],
                    newRank: insertTo.beforeItem
                        ? insertTo.beforeItem.item[rankField] + 1
                        : 1
                }

                if (
                    updateData.oriPid == updateData.newPid &&
                    updateData.newRank > updateData.oriRank
                ) {
                    //父元素没变，还在同一个内部移动
                    //并且向后挪了，从1 挪到2后面，它的sorkrank不是3，而是2，原来2的要变成1
                    updateData.newRank -= 1
                    //向前挪无所谓了，它从5挪到2的后面，那么它的sortrank就是3
                }
                if (
                    updateData.oriPid == updateData.newPid &&
                    updateData.oriRank == updateData.newRank
                ) {
                    return
                }
                keepMoveState(props.data, updateData)
                dragState.updating = true
                if (props.onDragEnd) {
                    const result = props.onDragEnd(updateData)
                    if (result instanceof Promise) {
                        result.then(() => {
                            dragState.updating = false
                        })
                    } else {
                        dragState.updating = false
                    }
                }
            }
        }
    )
}

/**
 * 检测是否拖入子级节点里了
 * @param self
 * @param pid
 */
function checkIntoSon(self, pid) {
    if (!props.treeConfig || !pid) {
        return false
    }
    if (self[rowConfig.value.keyField] == pid) {
        return true
    }
    const childField = props.treeConfig.childrenField
    if (self[childField]) {
        for (let item of self[childField]) {
            if (item[rowConfig.value.keyField] == pid || checkIntoSon(item, pid)) {
                return true
            }
        }
    }
    return false
}

//保持移动状态，等待刷新
function keepMoveState(list, state) {
    const { id, newPid, newRank } = state
    removeFromList(list, id)
    insertIntoList(list, dragState.dragItem.item, newPid, newRank)
    table.value.reloadData(list)
}

function removeFromList(list, id) {
    const keyField = rowConfig.value.keyField
    for (let i = 0; i < list.length; i++) {
        if (list[i][keyField] == id) {
            list.splice(i, 1)
            return true
        }
        if (props.treeConfig && list[i][props.treeConfig.childrenField]) {
            const success = removeFromList(
                list[i][props.treeConfig.childrenField],
                id
            )
            if (success) return true
        }
    }
}

/**
 * 向列表中插入
 * @param list
 * @param item
 * @param pid
 * @param newRank
 */
function insertIntoList(list, item, pid, newRank) {
    if (!pid || pid === '0') {
        list.splice(newRank - 1, 0, item)
        return
    }
    const keyField = rowConfig.value.keyField
    for (let i = 0; i < list.length; i++) {
        const children = list[i][props.treeConfig.childrenField]
        if (list[i][keyField] == pid) {
            children.splice(newRank - 1, 0, item)
            return true
        }

        if (children) {
            const success = insertIntoList(children, item, pid, newRank)
            if (success) return true
        }
    }
}

/**
 * 取消注册监听
 */
function unRegisterDragListener() {
    dragListener.value?.destroy()
}

/**        拖拽相关 end           */

defineExpose(
    //将vxeTable支持的api对外暴露
    extendEvents.reduce(
        (acc, k) => {
            acc[k] = (...args) => {
                return table.value[k](...args)
            }
            return acc
        },
        {
            dragState: dragState
        }
    )
)
provide('currentTable', getCurrentInstance())
</script>
<template>
    <vxe-table
        ref="table"
        :class="tableClass"
        :height="height"
        :border="border"
        :column-config="columnConfig"
        :show-header-overflow="showHeaderOverflow"
        :show-overflow="showOverflow"
        :row-config="rowConfig"
        :align="align"
        :seq-config="seqConfig"
        :loading="loading"
        :edit-config="{
            trigger: 'manual',
            mode: 'row',
            showStatus: true
        }"
        :size="props.size"
        :sort-config="sortConfig"
        :data="props.data"
        :tree-config="props.treeConfig"
        @header-cell-click="handleHeaderCellClick"
        @sortChange="handleSortChange"
    >
        <template v-for="(_item, key) in slots" :key="key" #[key]="data">
            <slot :name="key" v-bind="data ?? {}" />
        </template>
    </vxe-table>
</template>
<style scoped></style>
